#pragma once

#include "halley/text/halleystring.h"
#include "halley/utils/utils.h"
#include <gsl/gsl>
#include <array>
#include <optional>

#include "halley/data_structures/hash_map.h"
#include "halley/bytes/config_node_serializer_base.h"

namespace Halley {
	class ConfigNode;
	class Deserializer;
	class Serializer;

	class UUID {
    public:
        UUID();
        UUID(std::array<Byte, 16> bytes);
        explicit UUID(gsl::span<const std::byte> bytes);
		explicit UUID(const Bytes& bytes);
        explicit UUID(std::string_view str);
        explicit UUID(const ConfigNode& node);

        [[nodiscard]] static bool isUUID(std::string_view str);
        [[nodiscard]] static std::optional<UUID> tryParse(std::string_view str);

        bool operator==(const UUID& other) const;
        bool operator!=(const UUID& other) const;
		bool operator<(const UUID& other) const;

        UUID operator^(const UUID& other) const;

		String toString() const;
        ConfigNode toConfigNode() const;

        [[nodiscard]] static UUID generate();
        [[nodiscard]] static UUID xorUUIDs(const UUID& one, const UUID& two);
    	[[nodiscard]] bool isValid() const;

        gsl::span<const std::byte> getBytes() const;
		gsl::span<std::byte> getWriteableBytes();
        gsl::span<const uint64_t> getUint64Bytes() const;

    	void serialize(Serializer& s) const;
		void deserialize(Deserializer& s);

    private:
        union {
            std::array<uint64_t, 2> qwords;
            std::array<uint8_t, 16> bytes;
        };

        void setVersionBits();
	};

    template <>
    class ConfigNodeSerializer<UUID> {
    public:
        ConfigNode serialize(UUID id, const EntitySerializationContext& context);
        UUID deserialize(const EntitySerializationContext& context, const ConfigNode& node);
    };
}

template<>
struct std::hash<Halley::UUID>
{
    std::size_t operator() (const Halley::UUID& uuid) const noexcept
    {
        const auto& bytes = uuid.getUint64Bytes();
        return std::hash<uint64_t>()(Halley::combineHash64(bytes[0], bytes[1]));
    }
};

namespace natvis {
    struct x4lo {
    	uint8_t v: 4;
    	uint8_t _: 4;
    };
    struct x4hi {
	    uint8_t _ : 4;
    	uint8_t v : 4;
    };
    struct x8 {
	    uint8_t _;
    };
	struct x16 {
	    uint16_t _;
    };
    struct x32 {
	    uint32_t _;
    };
	struct x48 {
	    uint32_t b0;
		uint16_t b1;
    };
}
